package com.utils.osu;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSONObject;
import com.las.bot.common.Constant;
import com.las.bot.dao.OsuDao;
import com.las.bot.dao.OsuMapDao;
import com.las.bot.dto.OsuMapDTO;
import com.model.Osu;
import com.model.OsuMap;
import com.utils.DateUtils;
import com.utils.JsonUtils;
import com.jfinal.kit.StrKit;
import com.las.utils.mirai.CmdUtil;
import com.utils.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;

import static com.utils.osu.ModUtil.*;

@Component
public class OSUApi {

    @Autowired
    private OsuDao osuDao;

    @Autowired
    private OsuMapDao osuMapDao;



    /**
     * 将用户的信息set进缓存里，下次可以不用带参数名
     *
     * @param userId  用户ID（QQ号）
     * @param id      CQ的ID
     * @param type    CQ消息类型
     * @param osuName OSU玩家名字
     * @return
     */
    public String reset(long userId, long id, int type, String osuName) {
        if (StrKit.isBlank(osuName)) {
            //判断参数为空的情况，就找redis有没有记录
            Object username = RedisUtils.hget(Constant.OSU_USERTAG + userId, Constant.OSU_USERNAME);
            if (null == username) {
                CmdUtil.sendMessage("请在命令后面输入您游戏的名字吧~", userId, id, type);
            } else {
                //用户曾经输入过命令，还需要判断是不是在群里发出的命令
                if (Constant.MESSAGE_TYPE_GROUP == type) {
                    Map<String, Object> map = new HashMap<>();
                    map.put(String.valueOf(userId), username.toString().trim());
                    RedisUtils.hmset(Constant.OSU_USEGROUPTAG + id, map);
                }
            }
        } else {
            //不管用户第几次输入osuName，重新set进redis
            Map<String, Object> map = new HashMap<>();
            map.put(Constant.OSU_USERNAME, osuName.trim());
            RedisUtils.hmset(Constant.OSU_USERTAG + userId, map);
            //同时判断是群里发出来的，就添加一个群用户标记
            if (Constant.MESSAGE_TYPE_GROUP == type) {
                map = new HashMap<>();
                map.put(String.valueOf(userId), osuName.trim());
                RedisUtils.hmset(Constant.OSU_USEGROUPTAG + id, map);
            }

        }
        return RedisUtils.hget(Constant.OSU_USERTAG + userId, Constant.OSU_USERNAME).toString();
    }

    /**
     * 查询OSU成绩，返回msg给CQ
     *
     * @param username 用户名
     * @param ll       等级
     * @param pc       游玩次数
     * @param tth      总音符
     * @param ranks    排行
     * @param accs     准确率
     * @param pps      pp表现值
     * @param rankeds  排行上升值
     * @param mode     模式
     * @param update   是否更新
     * @return
     */
    public String sendOsuApi(String username, int ll, int pc, int tth, String ranks, String accs, String pps, String rankeds, int mode, boolean update) {
        String str;
        double pp;
        double acc;
        int rank;
        long ranked;
        List<Map> info = JsonUtils.getArrayByJson(ApiUtil.getOsuApiStr(username, mode), Map.class);
        if (CollectionUtil.isNotEmpty(info)) {
            if (null == info.get(0).get("playcount")) {
                return "玩家主页：https://osu.ppy.sh/u/" + info.get(0).get("user_id") + "\n" +
                        "这家伙在该模式中很久没玩过了>.<";
            }
            Osu myOSU = selInfoByUserId(Long.parseLong(info.get(0).get("user_id").toString()), mode);
            if (null == myOSU) {
                //用户是第一次使用，数据库不存在，于是执行插入
                Map<String, Object> m = new HashMap<>();
                m.put("userId", info.get(0).get("user_id"));
                m.put("acc", acc(info.get(0).get("accuracy").toString()));
                m.put("level", (int) Double.parseDouble(info.get(0).get("level").toString()));
                m.put("pc", info.get(0).get("playcount"));
                m.put("pp", acc(info.get(0).get("pp_raw").toString()));
                m.put("rank", info.get(0).get("pp_rank"));
                m.put("tth", add(info.get(0).get("count300").toString(), info.get(0).get("count100").toString(), info.get(0).get("count50").toString()));
                m.put("username", info.get(0).get("username"));
                m.put("mode", mode);
                m.put("ranked", info.get(0).get("ranked_score"));
                insertData(m);

            } else {
                if (update) {
                    //需要更新
                    myOSU.setAcc(Double.parseDouble(acc(info.get(0).get("accuracy").toString())));
                    myOSU.setLevel((int) Double.parseDouble(info.get(0).get("level").toString()));
                    myOSU.setPc(Integer.parseInt(info.get(0).get("playcount").toString()));
                    myOSU.setPp(Double.parseDouble(acc(info.get(0).get("pp_raw").toString())));
                    myOSU.setRank(Integer.parseInt(info.get(0).get("pp_rank").toString()));
                    myOSU.setTth(add(info.get(0).get("count300").toString(), info.get(0).get("count100").toString(), info.get(0).get("count50").toString()));
                    myOSU.setUserId(Long.parseLong(info.get(0).get("user_id").toString()));
                    myOSU.setUsername(info.get(0).get("username").toString());
                    myOSU.setUpdateTime(new Date());
                    myOSU.setRankscore(info.get(0).get("ranked_score").toString());
                    //myOSU.update();
                    osuDao.saveOrUpdate(myOSU);
                }
                ll = (int) Double.parseDouble(info.get(0).get("level").toString()) - myOSU.getLevel();
                pp = Double.parseDouble(acc(info.get(0).get("pp_raw").toString())) - myOSU.getPp();
                if (pp < 0) {
                    pps = "-" + acc("" + (-1) * pp);
                } else {
                    pps = "+" + acc("" + pp);
                }
                acc = Double.parseDouble(acc(info.get(0).get("accuracy").toString())) - myOSU.getAcc();
                if (acc < 0) {
                    accs = "-" + acc("" + (-1) * acc);
                } else {
                    accs = "+" + acc("" + acc);
                }
                pc = Integer.parseInt(info.get(0).get("playcount").toString()) - myOSU.getPc();
                tth = add(info.get(0).get("count300").toString(), info.get(0).get("count100").toString(), info.get(0).get("count50").toString()) - myOSU.getTth();
                rank = Integer.parseInt(info.get(0).get("pp_rank").toString()) - myOSU.getRank();
                if (rank <= 0) {
                    ranks = "↑" + (-1) * rank;
                } else {
                    ranks = "↓" + rank;
                }

                ranked = Long.parseLong(info.get(0).get("ranked_score").toString()) - Long.parseLong(myOSU.getRankscore());
                if (ranked < 0) {
                    rankeds = "-" + fen(((-1) * ranked) + "");
                } else {
                    rankeds = "+" + fen(ranked + "");
                }
            }
            String modes = ModUtil.getModeName(mode);

            String fen = 100 - (Double.parseDouble(info.get(0).get("level").toString()) - (int) Double.parseDouble(info.get(0).get("level").toString())) * 100 + "";

            str = "主页：https://osu.ppy.sh/u/" + info.get(0).get("user_id") + "\n" +
                    "玩家: " + info.get(0).get("username") + "\n" +
                    "模式: " + modes + "\n" +
                    "等级: " + (int) Double.parseDouble(info.get(0).get("level").toString()) + "（+" + ll + "）距升级剩" + acc(fen) + "%" + "\n" +
                    "表现: " + acc(info.get(0).get("pp_raw").toString()) + " pp（" + pps + "）" + "\n" +
                    "准确率: " + acc(info.get(0).get("accuracy").toString()) + "%" + "（" + accs + "）" + "\n" +
                    "总次数: " + info.get(0).get("playcount").toString() + "（+" + pc + "）" + "\n" +
                    "总命中: " + add(info.get(0).get("count300").toString(), info.get(0).get("count100").toString(), info.get(0).get("count50").toString()) + "（+" + tth + "）" + "\n" +
                    "成绩: " + fen(info.get(0).get("ranked_score").toString()) + "（" + rankeds + "）" + "\n" +
                    "全球排名: " + info.get(0).get("pp_rank").toString() + "（" + ranks + "）" + "\n";
            if (!update) {
                str += "[注]：括号里的数据是和上一次更新后的数据作比较\n更新需要输指令#update，具体可查看#osu帮助指令";
            } else {
                assert myOSU != null;
                str += "更新时间：" + DateUtils.format(myOSU.getUpdateTime());
            }

        } else {
            str = "找不到 " + username + " 该玩家的信息，可能网络炸了？！";
        }

        return str;
    }

    /**
     * 获取OSU的MAP歌谱，并且保存更新到数据库
     *
     * @param url    歌谱地址
     * @param sid    歌谱主id
     * @param bid    歌谱每个难度id
     * @param useApi 是否使用官方API查询最大PP值（后续可以做手动计算，等官方发PP算法才能弄）
     */
    public JSONObject getMap(String url, long sid, long bid, boolean useApi) {
        JSONObject myMap = new JSONObject();
        String sql = "select count(*) as total from osu_map where bid = " + bid;
        long b_count = osuMapDao.findFirstObj(sql).getLong("total");
        sql = "select count(*) as total from osu_map where sid = " + sid;
        long count = osuMapDao.findFirstObj(sql).getLong("total");
        if (0 == count && 0 == b_count) {
            Set<String> keys = new HashSet<>();
            keys.add("difficulty_rating");
            String info = Spider.spiderByKey(url, keys);
            if (StrKit.notBlank(info)) {
                String jsonString = JsonUtils.getJsonString(JsonUtils.convert(info));
                OsuMap map = JsonUtils.getObjectByJson(jsonString, OsuMap.class);
                sql = "select count(*) as total from osu_map where sid = " + map.getId();
                if (0 == osuMapDao.findFirstObj(sql).getLong("total")) {
                    //再检查一次
                    if (map.getStatus().contains("rank")) {
                        map.setSid(map.getId());
                        map.setId(null);
                        List<OsuMapDTO> osuMapList = new ArrayList<>();
                        List<JSONObject> beatmaps = JsonUtils.getArrayByJson(JsonUtils.getJsonObjectByJsonString(info).getString("beatmaps"), JSONObject.class);
                        if (CollectionUtil.isNotEmpty(beatmaps)) {
                            for (JSONObject object : beatmaps) {
                                OsuMapDTO osuMap = JsonUtils.getObjectByJson(JsonUtils.getJsonString(map), OsuMapDTO.class);
                                osuMap.setBid(object.getLong("id"));
                                osuMap.setMode(object.getInteger("mode_int"));
                                osuMap.setDifficultyRating(object.getDouble("difficulty_rating"));
                                osuMap.setTotalLength(object.getInteger("total_length"));
                                osuMap.setCs(object.getDouble("cs"));
                                osuMap.setDrain(object.getDouble("drain"));
                                osuMap.setAccuracy(object.getDouble("accuracy"));
                                osuMap.setAr(object.getDouble("ar"));
                                String[] split = object.getString("last_updated").split("T");
                                Date date = DateUtils.formatToDate(DateUtils.DAY, split[0]);
                                osuMap.setLastUpdated(date);
                                osuMap.setStatus(object.getString("status"));
                                //再加一个新字段，版本名
                                osuMap.setVersion(object.getString("version"));
                                if (useApi) {
                                    String json = ApiUtil.getScoreWithMod(0, osuMap.getMode(), String.valueOf(osuMap.getBid()));
                                    if (StrKit.notBlank(json)) {
                                        List<JSONObject> list = JsonUtils.getArrayByJson(json, JSONObject.class);
                                        if (CollectionUtil.isNotEmpty(list)) {
                                            osuMap.setMaxPp(list.get(0).getDouble("pp"));
                                        }
                                    }
                                }
                                if (bid == osuMap.getBid()) {
                                    BeanUtil.copyProperties(osuMap, myMap);
                                }
                                osuMapList.add(osuMap);
                            }
                        }
                        if (CollectionUtil.isNotEmpty(osuMapList)) {
                            for (OsuMap osumap : osuMapList) {
                                osuMapDao.saveOrUpdate(osumap);
                            }
                        }
                    }
                } else {
                    myMap = updateMaxByMap(url, bid, useApi);
                }
            }
        } else {
            myMap = updateMaxByMap(url, bid, useApi);
        }
        return myMap;
    }



    /**
     * 根据歌谱bid获取最新数据信息
     * @param bid
     * @return OsuMapDTO对象信息
     */
    public OsuMapDTO getMapByBid(Long bid) {
        Set<String> keys = new HashSet<>();
        keys.add("difficulty_rating");
        String info = Spider.spiderByKey("https://osu.ppy.sh/b/" + bid, keys);
        if (StrKit.notBlank(info)) {
            String jsonString = JsonUtils.getJsonString(JsonUtils.convert(info));
            OsuMap map = JsonUtils.getObjectByJson(jsonString, OsuMap.class);
            if (map.getStatus().contains("rank")) {
                map.setSid(map.getId());
                map.setId(bid);
                List<JSONObject> beatmaps = JsonUtils.getArrayByJson(JsonUtils.getJsonObjectByJsonString(info).getString("beatmaps"), JSONObject.class);
                if (CollectionUtil.isNotEmpty(beatmaps)) {
                    for (JSONObject object : beatmaps) {
                        if (bid.equals(object.getLong("id"))) {
                            OsuMapDTO osuMap = JsonUtils.getObjectByJson(JsonUtils.getJsonString(map), OsuMapDTO.class);
                            osuMap.setBid(object.getLong("id"));
                            osuMap.setMode(object.getInteger("mode_int"));
                            osuMap.setDifficultyRating(object.getDouble("difficulty_rating"));
                            osuMap.setTotalLength(object.getInteger("total_length"));
                            osuMap.setCs(object.getDouble("cs"));
                            osuMap.setDrain(object.getDouble("drain"));
                            osuMap.setAccuracy(object.getDouble("accuracy"));
                            osuMap.setAr(object.getDouble("ar"));
                            String[] split = object.getString("last_updated").split("T");
                            Date date = DateUtils.formatToDate(DateUtils.DAY, split[0]);
                            osuMap.setLastUpdated(date);
                            osuMap.setStatus(object.getString("status"));
                            //再加一个新字段，版本名
                            osuMap.setVersion(object.getString("version"));
                            return osuMap;
                        }
                    }
                }
            }
        }
        return null;
    }


    /**
     * 获取OSU玩家排名信息
     *
     * @param osuName 玩家名
     * @param mode    模式
     * @param names   玩家名集合数组字符串
     */
    public JSONObject getOsuRank(String osuName, int mode, String names) {
        String sql = "select username,rownum,(select count(*) as count from osu where mode = c.mode and username in(" + names + ") group by mode having count>0) as total," +
                "    level,pp from ( " +
                "                    select (@i:=case when mode=@pre_code then @i + 1 else 1 end) rownum," +
                "                            user_id,username,mode,level,pp,(@pre_code:=mode) " +
                "                    from osu a,(select @i:=0, @pre_code:='') b " +
                "                    where pp is not null and mode = " + mode + " and username in(" + names + ") " +
                "                    order by mode,pp desc " +
                "    ) c " +
                "    where username = '" + osuName + "' " +
                "    limit 1";
        return osuDao.findFirstObj(sql);
    }




    /**
     * 根据用户ID和模式，找其对应OSU玩家的游戏ID
     *
     * @param userId 用户ID
     * @param mode   模式
     * @return
     */
    public Osu selInfoByUserId(long userId, Integer mode) {
        String sql = "select id from osu where user_id = ? and mode = ?";
        Osu osu = osuDao.findFirst(sql, userId, mode);
        if (null != osu) {
            return osuDao.findById(osu.getId());
        }
        return null;
    }


    /**
     * 插入玩家用户成绩进数据库
     *
     * @param m 成绩Map信息
     */
    public void insertData(Map<String, Object> m) {
        Osu myOSU = new Osu();
        myOSU.setUserId(Long.parseLong(m.get("userId").toString()));
        myOSU.setAcc(Double.parseDouble(m.get("acc").toString()));
        myOSU.setLevel(Integer.parseInt(m.get("level").toString()));
        myOSU.setPc(Integer.parseInt(m.get("pc").toString()));
        myOSU.setPp(Double.parseDouble(m.get("pp").toString()));
        myOSU.setRank(Integer.parseInt(m.get("rank").toString()));
        myOSU.setTth(Integer.parseInt(m.get("tth").toString()));
        myOSU.setUsername(m.get("username").toString());
        myOSU.setMode(Integer.parseInt(m.get("mode").toString()));
        myOSU.setCreateTime(new Date());
        myOSU.setUpdateTime(new Date());
        myOSU.setRankscore(m.get("ranked").toString());
        //myOSU.save();
        osuDao.saveOrUpdate(myOSU);
    }


    /**
     * 根据歌谱id 更新最大pp值（以及其他信息）
     *
     * @param bid    歌谱ID
     * @param useApi 是否使用官方API查询最大PP值（后续可以做手动计算，等官方发PP算法才能弄）
     */
    public JSONObject updateMaxByMap(String url, long bid, boolean useApi) {
        String mySql = "select * from osu_map where bid = " + bid;
        JSONObject myMap = osuMapDao.findFirstObj(mySql);
        if (null != myMap) {
            //还有歌谱的难度每时每刻或许也会变，需要再去爬取去抓一下
            Set<String> keys = new HashSet<>();
            keys.add("difficulty_rating");
            String info = Spider.spiderByKey(url, keys);
            if (StrKit.notBlank(info)) {
                String jsonString = JsonUtils.getJsonString(JsonUtils.convert(info));
                OsuMap map = JsonUtils.getObjectByJson(jsonString, OsuMap.class);
                if (map.getStatus().contains("rank")) {
                    map.setSid(map.getId());
                    map.setId(myMap.getLong("id"));//因为是做更新
                    List<JSONObject> beatmaps = JsonUtils.getArrayByJson(JsonUtils.getJsonObjectByJsonString(info).getString("beatmaps"), JSONObject.class);
                    if (CollectionUtil.isNotEmpty(beatmaps)) {
                        for (JSONObject object : beatmaps) {
                            if (myMap.getLong("id").equals(object.getLong("id"))) {
                                OsuMapDTO osuMap = JsonUtils.getObjectByJson(JsonUtils.getJsonString(map), OsuMapDTO.class);
                                osuMap.setBid(object.getLong("id"));
                                osuMap.setMode(object.getInteger("mode_int"));
                                osuMap.setDifficultyRating(object.getDouble("difficulty_rating"));
                                osuMap.setTotalLength(object.getInteger("total_length"));
                                osuMap.setCs(object.getDouble("cs"));
                                osuMap.setDrain(object.getDouble("drain"));
                                osuMap.setAccuracy(object.getDouble("accuracy"));
                                osuMap.setAr(object.getDouble("ar"));
                                String[] split = object.getString("last_updated").split("T");
                                Date date = DateUtils.formatToDate(DateUtils.DAY, split[0]);
                                osuMap.setLastUpdated(date);
                                osuMap.setStatus(object.getString("status"));
                                //再加一个新字段，版本名
                                osuMap.setVersion(object.getString("version"));
                                if (useApi) {
                                    String json = ApiUtil.getScoreWithMod(0, osuMap.getMode(), String.valueOf(osuMap.getBid()));
                                    if (StrKit.notBlank(json)) {
                                        List<JSONObject> list = JsonUtils.getArrayByJson(json, JSONObject.class);
                                        if (CollectionUtil.isNotEmpty(list)) {
                                            osuMap.setMaxPp(list.get(0).getDouble("pp"));
                                        }
                                    }
                                }
                                osuMapDao.saveOrUpdate(osuMap);
                                BeanUtil.copyProperties(osuMap, myMap);
                                //更新一个歌谱就跳出循环即可
                                break;
                            }
                        }
                    }
                }
            }
        }
        return myMap;
    }


    /**
     * 原创算法，推荐歌谱给玩家，突破玩家BP的极限
     *
     * @param mode 模式
     * @param bpMe bp极限PP值
     * @param list 玩家BP列表中数据
     */
    public List<OsuMap> getOsuMaps(int mode, double bpMe, List<JSONObject> list) {
        int x = (int) (Math.random() * list.size() * 0.05);
        double maxPp = list.get(x).getDouble("pp");
        double abs = Math.abs(bpMe - maxPp) * 0.5;
        abs = abs == 0 ? 5 : abs;
        String sql = "select * from osu_map where mode = " + mode + " and max_pp between " + bpMe + " and " + (bpMe + abs);
        return osuMapDao.findList(sql);
    }

}
